package net.gdface.cassdk;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.logging.Logger;

import net.gdface.utils.ResourcePool.IntResourcePool;

public abstract class BaseJniBridge {
	protected static final Logger logger = Logger.getLogger(BaseJniBridge.class.getSimpleName());
	protected static final String CASSDK_HOME = getCassdkHome();
	protected static final int DEFAULT_SAMPLE_SIZE;
	/**
	 * {@link FacePos#facialData} 面部数据长度
	 */
	public static final int FACEPOS_FACIALDATA_LEN = 512;
	protected static final String INVALID_LICENSE = "invalid license";
	protected static final String INVALID_CHANNEL = "nChannelID is invalid or SDK is not initialized";
	protected static final String INVALID_IMAGE = "image data is invalid,please check function parameter:pImage,bpp,nWidth,nHeight";
	protected static final String INVALID_FPS = "pfps or nMaxFaceNums is invalid";
	protected static final String INVALID_PBUF = "pBuf,ptfp,pFeature is NULL";
	/**
	 * 模型数据复制标记，用于保证只在初始化时被执行一次
	 */
	static boolean modelCopyed = false;
	protected static IntResourcePool detectChannelPool;
	protected static IntResourcePool featureChannelPool;
	protected static int maxDetectFaceNum;
	protected static int sampleSize;
	/**
	 * 特征码长度常量
	 */
	public static int FEATURE_SIZE;
	static {
		DEFAULT_SAMPLE_SIZE = CasConfig.getSampleSize();
		maxDetectFaceNum = CasConfig.getMaxDetectFaceNum();
		sampleSize = DEFAULT_SAMPLE_SIZE;
	}
	/**
	 * 复制 CASSDK运行所需的模型数据到Java当前目录user.dir<br>
	 * 必须定义 cassdk_home变量,用于指定CASSDK的目录位置<br>
	 * 定义cassdk_home变量的方法
	 * 1. 定义环境变量CASSDK_HOME
	 * 2. java运行时必须用-D选项定义cassdk_home
	 * 优先在环境变量中查找CASSDK_HOME,如果找不到,再在系统变量定义中查找cassdk_home<br>
	 * 如果都找不到则抛出异常<br>
	 * 假定模型数据在${cassdk_home}/bin下
	 */
	protected static final synchronized void copyModelToUserDir() {
		if(modelCopyed){
			return;
		}
		File cassdkPath=new File(new File(CASSDK_HOME),"bin");
			
		// 检查 "cassdk_home"的合法性
		if(!cassdkPath.exists()||!cassdkPath.isDirectory()){
			throw new ExceptionInInitializerError("NOT FOUND FOLDER:".concat(CASSDK_HOME));
		}
		// 根据操作系统类型确定文件复制的目标路径
		File destDir;
		if(System.getProperty("os.name").toLowerCase().startsWith("win")){
			// Windows版本SDK的加载数据文件是可执行程序所在路径(java下即JVM程序)
			final String javaLibraryPath = System.getProperty("java.library.path");
			// 以java.library.path中的第一个路径为jvm所在位置 
			destDir = new File( javaLibraryPath.substring(0, javaLibraryPath.indexOf(';')));
			if(!new File(destDir,"java.exe").exists()){
				throw new ExceptionInInitializerError("CANT NOT locate JVM folder(无法定位JVM路径)");
			}
			logger.info(String.format("JVM 路径 %s",destDir.toString()));			
		}else{
			// linux版本SDK的加载数据文件是用户当前路径
			destDir=new File(System.getProperty("user.dir"));
		}
		logger.info(String.format("copy model files from %s to %s", cassdkPath.getAbsolutePath(),destDir.getAbsolutePath()));
		try {
			// 如果destDir与"cassdk_dir"相等则放弃复制
			if(!destDir.getCanonicalPath().equals(cassdkPath.getCanonicalPath())){
				for(String file:CasConfig.getVersion().getModelFiles()){
					File facedb = new File(cassdkPath,file);
					if(needCopy(facedb,destDir)){
						try{
							logger.info(String.format("copy file %s", file));
							copyAndDeleteOnExit(facedb,destDir);
						}catch (RuntimeException e) {
							if(e.getCause() instanceof IOException){
								throw new ExceptionInInitializerError(
										String.format("无法从\n%s\n复制模型文件(%s)到\n%s\n请手工复制\n因为:%s", 
												cassdkPath.toString(),
												file,
												destDir.toString(),
												e.getMessage()));
							}
							throw e;
						} 
					}	
				}
			}
			modelCopyed=true;
		} catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	/**
	 * 查找 cassdk_home变量,优先在环境变量中查找CASSDK_HOME,如果找不到,再在系统变量定义中查找cassdk_home<br>
	 * 如果都找不到则抛出异常
	 * @return
	 */
	static final String getCassdkHome() {
		String cassdkHome=System.getenv("CASSDK_HOME");
		if(null!=cassdkHome){
			logger.info(String.format("environment variable CASSDK_HOME=%s", cassdkHome));
		}else{
			if(null==(cassdkHome=System.getProperty("cassdk_home"))){
				throw new ExceptionInInitializerError("UNDEFINED cassdk_home,you can defined the variable by java -Dcassdk_home=<value> OR set environment variable 'CASSDK_HOME'");				
			}
			logger.info(String.format("cassdk_home=%s,defined by java -D<name>=<value>", cassdkHome));
		}
		return cassdkHome;	
	}

	/**
	 * 递归判断文件/文件夹是否需要复制到目标文件夹<br>
	 * 判断条件:目标不存在或大小不相等,源不存在则返回false
	 * @param src 原文件/文件夹
	 * @param dstFolder 目标文件夹 
	 */
	static final boolean needCopy(final File src, final File dstFolder) {
		if(null==src||null==dstFolder){
			throw new NullPointerException("src or dstFolder is null");
		}
		if(!src.exists()){
			return false;
		}
		if(!dstFolder.exists()){
			return true;
		}
		if(src.isDirectory()){
			File[] noeq = src.listFiles(new FileFilter(){
				@Override
				public boolean accept(File pathname) {
					File folder = new File(dstFolder,src.getName());
					return needCopy(pathname,folder);
				}});
			return noeq.length>0; 
		}else{
			File dst=new File(dstFolder,src.getName());
			return (!dst.isFile())||(dst.length()!=src.length());
		}
	}

	/**
	 * 递归复制文件/文件夹到指定的目录，并且在JVM结束时删除
	 * @param src 原文件/文件夹
	 * @param dstFolder 目标文件夹 
	 * @throws RuntimeException  {@link IOException}被封装成 {@link RuntimeException}抛出
	 */
	static final void copyAndDeleteOnExit(final File src, final File dstFolder) throws RuntimeException {
		if(null==src||null==dstFolder){
			throw new NullPointerException("src or dstFolder is null");
		}
		if(!src.exists()){
			return;
		}
		if(src.isDirectory()){
			src.listFiles(new FileFilter(){
				@Override
				public boolean accept(File pathname) {
					File folder = new File(dstFolder,src.getName());
					folder.deleteOnExit(); // JVM结束时删除指定文件夹
					copyAndDeleteOnExit(pathname,folder);					
					return false;
				}});
		}else{
			try {
				File dst=new File(dstFolder,src.getName());
				nioCopyFile(src,dst);
				// JVM结束时删除文件
				dst.deleteOnExit();
			} catch (IOException e) {			
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * NIO方式复制文件<br>
	 * 目标文件所在的文件夹如果不存在自动创建文件夹
	 * @param src 源文件
	 * @param dst 目标文件 
	 * @throws IOException
	 */
	static final void nioCopyFile(File src, File dst) throws IOException {
		if(null==src||null==dst){
			throw new NullPointerException("src or dst is null");
		}
		if(!src.exists()||!src.isFile()){
			throw new IllegalArgumentException(String.format("INVALID FIILE NAME(无效文件名) src=%s",src.getCanonicalPath()));
		}
		if (dst.exists() &&!dst.isFile()) {
			throw new IllegalArgumentException(String.format("INVALID FIILE NAME(无效文件名) dst=%s",dst.getCanonicalPath()));
		}
		File folder = dst.getParentFile();
		if (!folder.exists()){
			folder.mkdirs();
		}
		if(((src.length()+(1<<10)-1)>>10)>(folder.getFreeSpace()>>10)){
			throw new IOException(String.format("DISK ALMOST FULL(磁盘空间不足) %s",folder.getCanonicalPath()));
		}
		FileInputStream fin=null; 
		FileOutputStream fout = null;
		FileChannel fic = null;
		FileChannel foc = null;
		try {
			fin=new FileInputStream(src);
			fout = new FileOutputStream(dst);
			fic = fin.getChannel();
			foc = fout.getChannel();
			// 1MB缓冲区
			ByteBuffer bb = ByteBuffer.allocate(1024<<10);
			while(fic.read(bb)>0){				
				bb.flip();
				foc.write(bb);				
				bb.clear();
			}
		} finally {
			// 安全释放资源
			if(null!=fic){
				fic.close();
			}
			if(null!=foc){
				foc.close();
			}
			if(null!=fin){
				fin.close();
			}
			if(null!=fout){
				fout.close();
			}
		}
	}

	/**
	 * 
	 * 设置最大可检测人脸的数目，默认为256<br>
	 * 用于新线程初始化人脸检测结果数据缓冲区大小<br>
	 * 此值为全局变量,对在此方法调用之后新建的线程有效
	 * @param maxNum 必须>0,否则抛出异常
	 * @throws IllegalArgumentException 当maxNum<=0
	 */
	public static synchronized void setMaxDetectFaceNum(int maxNum) {
		if(maxNum <= 0){
			throw new IllegalArgumentException(String.format("Invalid maxNum:%d",maxNum));
		}
		maxDetectFaceNum = maxNum;
	}

	public static int getMaxDetectFaceNum() {
		return maxDetectFaceNum;
	}

	public static int getSampleSize() {
		return sampleSize;
	}

	/**
	 * 设置人脸检测时的图像归一化尺寸
	 * @param sampleSize 为0使用SDK提供的默认尺寸(参见SDK中THFI_DetectFace函数定义),<0则抛出异常
	 * @throws IllegalArgumentException sampleSize<0
	 */
	public static void setSampleSize(int sampleSize) {
		if(sampleSize < 0){
			throw new IllegalArgumentException(String.format("Invalid sampleSize:%d",sampleSize));
		}
		BaseJniBridge.sampleSize = sampleSize;
	}

	protected BaseJniBridge() {
	}
}

